/*-------------------------------------------------------------------------
 *
 * pg_largeobject.c
 *      routines to support manipulation of the pg_largeobject relation
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/backend/catalog/pg_largeobject.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_largeobject_metadata.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/rel.h"
#include "utils/tqual.h"


/*
 * Create a large object having the given LO identifier.
 *
 * We create a new large object by inserting an entry into
 * pg_largeobject_metadata without any data pages, so that the object
 * will appear to exist with size 0.
 */
Oid
LargeObjectCreate(Oid loid)
{
    Relation    pg_lo_meta;
    HeapTuple    ntup;
    Oid            loid_new;
    Datum        values[Natts_pg_largeobject_metadata];
    bool        nulls[Natts_pg_largeobject_metadata];

    pg_lo_meta = heap_open(LargeObjectMetadataRelationId,
                           RowExclusiveLock);

    /*
     * Insert metadata of the largeobject
     */
    memset(values, 0, sizeof(values));
    memset(nulls, false, sizeof(nulls));

    values[Anum_pg_largeobject_metadata_lomowner - 1]
        = ObjectIdGetDatum(GetUserId());
    nulls[Anum_pg_largeobject_metadata_lomacl - 1] = true;

    ntup = heap_form_tuple(RelationGetDescr(pg_lo_meta),
                           values, nulls);
    if (OidIsValid(loid))
        HeapTupleSetOid(ntup, loid);

    loid_new = CatalogTupleInsert(pg_lo_meta, ntup);
    Assert(!OidIsValid(loid) || loid == loid_new);

    heap_freetuple(ntup);

    heap_close(pg_lo_meta, RowExclusiveLock);

    return loid_new;
}

/*
 * Drop a large object having the given LO identifier.  Both the data pages
 * and metadata must be dropped.
 */
void
LargeObjectDrop(Oid loid)
{
    Relation    pg_lo_meta;
    Relation    pg_largeobject;
    ScanKeyData skey[1];
    SysScanDesc scan;
    HeapTuple    tuple;

    pg_lo_meta = heap_open(LargeObjectMetadataRelationId,
                           RowExclusiveLock);

    pg_largeobject = heap_open(LargeObjectRelationId,
                               RowExclusiveLock);

    /*
     * Delete an entry from pg_largeobject_metadata
     */
    ScanKeyInit(&skey[0],
                ObjectIdAttributeNumber,
                BTEqualStrategyNumber, F_OIDEQ,
                ObjectIdGetDatum(loid));

    scan = systable_beginscan(pg_lo_meta,
                              LargeObjectMetadataOidIndexId, true,
                              NULL, 1, skey);

    tuple = systable_getnext(scan);
    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("large object %u does not exist", loid)));

    CatalogTupleDelete(pg_lo_meta, &tuple->t_self);

    systable_endscan(scan);

    /*
     * Delete all the associated entries from pg_largeobject
     */
    ScanKeyInit(&skey[0],
                Anum_pg_largeobject_loid,
                BTEqualStrategyNumber, F_OIDEQ,
                ObjectIdGetDatum(loid));

    scan = systable_beginscan(pg_largeobject,
                              LargeObjectLOidPNIndexId, true,
                              NULL, 1, skey);
    while (HeapTupleIsValid(tuple = systable_getnext(scan)))
    {
        CatalogTupleDelete(pg_largeobject, &tuple->t_self);
    }

    systable_endscan(scan);

    heap_close(pg_largeobject, RowExclusiveLock);

    heap_close(pg_lo_meta, RowExclusiveLock);
}

/*
 * LargeObjectExists
 *
 * We don't use the system cache for large object metadata, for fear of
 * using too much local memory.
 *
 * This function always scans the system catalog using an up-to-date snapshot,
 * so it should not be used when a large object is opened in read-only mode
 * (because large objects opened in read only mode are supposed to be viewed
 * relative to the caller's snapshot, whereas in read-write mode they are
 * relative to a current snapshot).
 */
bool
LargeObjectExists(Oid loid)
{
    Relation    pg_lo_meta;
    ScanKeyData skey[1];
    SysScanDesc sd;
    HeapTuple    tuple;
    bool        retval = false;

    ScanKeyInit(&skey[0],
                ObjectIdAttributeNumber,
                BTEqualStrategyNumber, F_OIDEQ,
                ObjectIdGetDatum(loid));

    pg_lo_meta = heap_open(LargeObjectMetadataRelationId,
                           AccessShareLock);

    sd = systable_beginscan(pg_lo_meta,
                            LargeObjectMetadataOidIndexId, true,
                            NULL, 1, skey);

    tuple = systable_getnext(sd);
    if (HeapTupleIsValid(tuple))
        retval = true;

    systable_endscan(sd);

    heap_close(pg_lo_meta, AccessShareLock);

    return retval;
}
